package pCloud.stream;

import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Stack;


import pCloud.PCloudAttributeAndType;

public class Auxiliary {

	
	
private Vector<String> filterMergeInfo;
	
	public Auxiliary() {
		this.filterMergeInfo = new Vector<String>();
		
	}
	
	public int getPrecedence(char op) {
		
		switch(op) {
		case '|': return 1;
		case '&': return 2;
		default: return 0;		
		}		
	}
	
	// this function returns a string contain merging info according to a given level.
	// currently only show warning caused by user query.
	// level 0 : return all information available  
	// level 1 : return warning type, but not affected predicates
	// level 2 : return just warning message.
	
	public String showConditionMergeInfo(int level){
		String info = "";
		switch (level) {
		
			case 0: 
						for (int i=0; i<this.filterMergeInfo.size(); i++){
							info += this.filterMergeInfo.elementAt(i) + Global.eol;	
						}
						
						break;
			
			case 1: 
						boolean NRFound = false;
						boolean PRFound = false;
						for (int i=0; i<this.filterMergeInfo.size() && !NRFound;i++){
							
							if (this.filterMergeInfo.elementAt(i).contains("USER QUERY WARNING: NO RESULT")) {
								NRFound = true;
							}
							if (this.filterMergeInfo.elementAt(i).contains("USER QUERY WARNING: ONLY PARTIAL")) {
								PRFound = true;
							}
						}
						if (NRFound) {
							info = "Empty Result Set Warning." + Global.eol;
						}
						else if (PRFound) {
							info = "Partial Result Set Warning." + Global.eol;

						}
						
						break;
			
			case 2: if (this.filterMergeInfo.size() > 0){
							info += "User query may not get any result or may only get partial result due to policy settings!";
						}
				
						break;
	
			default : info = "Invalid Report Level in Policy";
		
		}
		
		return info;
	}
	
	
	// this function checks a boolean expression  (of the form X | Y | Z ...)
	// to see if it will result in empty/partial result set.
	
	public boolean checkAllConditions(String s){
		
		
		StringTokenizer st = new StringTokenizer(s,"|");
		boolean hasProblem = true;
		
		boolean noProblem = false; 
		
		while(st.hasMoreTokens()){
			String andCondition = st.nextToken();
			System.out.println("Checking " + andCondition + "...");
			hasProblem = this.checkAndConditions(andCondition);
			if (!hasProblem){
				noProblem = true;	
			}						
		}
		
		if (noProblem) {
			
			return true;
		}
		else {
			return false;
		}
	}
	
	
	// this function checks a boolean expression  (in the form x1 & x2 & x3 & x4... xn)
	// to see if it will result in empty/partial result set.
	// affected predicate will be store in class variable filterMergeInfo.
	
	public boolean checkAndConditions(String s) {
		
		boolean hasProblem = false;
		
		// only call simpleExpression's mergeAnd method when simple expression's variables are the same.
		
		Vector<SimpleExpression> exprs = new Vector<SimpleExpression>();
		
		StringTokenizer st = new StringTokenizer(s,"&");
		while (st.hasMoreTokens()) {
			exprs.add(new SimpleExpression(st.nextToken()));
		}
		
		for (int i=0; i<exprs.size()&&!hasProblem; i++){
			SimpleExpression se = exprs.elementAt(i);
			for (int j=i+1; j<exprs.size()&&!hasProblem;j++){
				if (se.variable.equalsIgnoreCase(exprs.elementAt(j).variable)){
					
					System.out.println("Merging " + se.toString() + " and " + exprs.elementAt(j).toString());
					String mergeResult = se.merge(exprs.elementAt(j));
		//			System.out.println(mergeResult);
					
					if (mergeResult.contains("USER QUERY WARNING") | mergeResult.contains("GENERAL WARNING")) {
						
						hasProblem = true;
						this.filterMergeInfo.add(mergeResult);						
					}
				}								
			}									
		}
		
		return hasProblem;
		
	}
	
	
	// this function combines two boolean expression into the form 
	// x1 & x2.. xn | y1 & y2 & y3 | z1 & z2 & z3 ....
	// for further checking of result set ( to see if normal/partial/no results would be given)	
	
	public String boolDistributionAnd(String e1, String e2) {
		
		String result ="";
		
		Vector<String> expr1 = new Vector<String>();
		Vector<String> expr2 = new Vector<String>();
		
		StringTokenizer st1 = new StringTokenizer(e1,"|");
		StringTokenizer st2 = new StringTokenizer(e2,"|");

		while (st1.hasMoreTokens()){
			expr1.add(st1.nextToken());
		}
		
		while (st2.hasMoreTokens()){
			expr2.add(st2.nextToken());
		}
		
	/*
		for (int i=0;i<expr1.size();i++){
			System.out.println(expr1.elementAt(i));
		}
		
		for (int i=0;i<expr2.size();i++){
			System.out.println(expr2.elementAt(i));
		}
		
	*/	
		
		for (int  i=0; i<expr1.size();i++){
			
			for (int j=0; j<expr2.size(); j++){
				
				result += expr1.elementAt(i) + "&" + expr2.elementAt(j) + "|";
			}
		}
		
		// remove ending "|";
		result = result.substring(0, result.lastIndexOf('|'));
		
		return result;
		
	}
	
	
	
	// parse expression to vector of strings, delimiters are && , ||, ( , and )
	public Vector<String> parseExpression(String expr, int cid){
		
		Vector<String> result = new Vector<String>();
		
		expr = expr.replace("&&", "&");
		expr = expr.replace("||", "|");
		expr=expr.replaceAll("\\s+", ""); // remove all white spaces.
		
		StringTokenizer st = new StringTokenizer(expr,"&|()",true);
		
		while(st.hasMoreTokens()){
			
			String s = st.nextToken();
			if (!s.equals("&") && !s.equals("|") && !s.equals("(") && !s.equals(")") ){
				s = s+ ":" + cid;
				
			}
			result.add(s);
		}
		
		/*
		for (int i=0; i<result.size();i++){
			System.out.println(result.elementAt(i));
		}
		*/		
		return result;		
	}
	

	// this functin evaluate a post fix boolean expression. 
	// the result is a string of form x1 & x2 ... xn | y1 & y2 ... yn | z1 & z2 & ... zn | ...
	// the result string will be checked by checkAllConditions function to see if a NR or PR case takes place.
	// this function uses boolDistributionAnd function.
	
	public String evaluatePostFix(Vector<String> expr) {
		
		String result = "";
		Stack<String> opStack = new Stack<String>();
		
		for (int i=0; i<expr.size();i++){
			
			String current = expr.elementAt(i);
			if (!current.equals("&") && !current.equals("|")){
				opStack.push(current);
				
			}
			else {
				
				String op1 = opStack.pop();
				String op2 = opStack.pop();
				
				if (current.equals("|")){
					result = op1 + "|" + op2;
					opStack.push(result);
				}
				else if (current.equals("&")){
					result = this.boolDistributionAnd(op1, op2);
					opStack.push(result);
				}
			}
		}
		result = opStack.pop();
		return result;	
		
	}

	
	public Vector<String> toPostfix(Vector<String> infix){
		
		Vector<String> result = new Vector<String>();

		Stack <Character> opStack = new Stack<Character>();
		opStack.push(new Character('\u0000'));

		for (int i=0; i<infix.size();i++){
			String s = infix.elementAt(i);
			
			if (s.equals("(")) {
				opStack.push('(');
				
			}
			else if (s.equals("&") || s.equals("|")){
				char c = s.equals("&")?'&':'|';
				while ((this.getPrecedence(opStack.peek().charValue()))>= (this.getPrecedence(c))) {
					result.add(opStack.pop().toString());
				}
				opStack.push(c);
			}
			else if (s.equals(")")){
				while ((opStack.peek().charValue()) != '(' ){
					result.add(opStack.pop().toString());
				}
				opStack.pop();
			}
			else {
				result.add(s);
			}
		}
		
		while (opStack.peek().charValue() != '\u0000'){
			result.add(opStack.pop().toString());
		}
		

		
		return result;
	}
	
	//merge two filter boxes, fb1 is the one from original policy, fb2 is from user query

	public static FilterBox mergeFilterBox(FilterBox fb1, FilterBox fb2){
		
		FilterBox result = new FilterBox();
		
		result.filtercondition = fb1.filtercondition + " && " + fb2.filtercondition;
		return result;
	}
	
	
	//merge two map boxes, mb1 is the one from original policy, mb2 is from user query
	public static MapBox mergeMapBox(MapBox mb1, MapBox mb2) {
		
		MapBox result = new MapBox();
		String info = "";
		
		Vector<String> unreportattribute = new Vector<String>();
		
		Vector<String> reportattribute = new Vector<String>();
				
		for (int i=0; i<mb2.attributes.size(); i++){
			
			if (!mb1.attributes.contains(mb2.attributes.elementAt(i))) {
				unreportattribute.add(mb2.attributes.elementAt(i));
			}
			else {
				
				reportattribute.add(mb2.attributes.elementAt(i));
			}
						
		}
		
		if (reportattribute.isEmpty()) {
			
			info += PCloudAttributeAndType.NO_RESULT_WARNING;
		}
		
		else if (!unreportattribute.isEmpty()){
			
			info += PCloudAttributeAndType.PARTIAL_RESULT_WARNING + Global.eol;
			info += "Following Attributes will not be reported: " + Global.eol;
			for (int i=0; i<unreportattribute.size(); i++){
				info += unreportattribute.elementAt(i) + Global.eol;
			}
		}
		
		else {
			
		}
		
		result.mergeMsg = info;
		result.attributes = reportattribute;
	
		return result;
		
	}
	
	
	//merge two aggregate boxes, ab1 is the one from original policy, ab2 is from user query

	public static AggregateBox mergeAggregateBox(AggregateBox ab1, AggregateBox ab2) {
		
		AggregateBox result = new AggregateBox();
		
		Vector<String> reportfunction = new Vector<String>();
		Vector<String> reportattribute = new Vector<String>();
		
		Vector<String> unreportfunction = new Vector<String>();
		Vector<String> unreportattribute = new Vector<String>();
		
		String info = "";
		
		// checking window size, type and step. 
		
		if (!ab1.windowtype.equals(ab2.windowtype)){
			
			info += PCloudAttributeAndType.WINDOW_ERROR + Global.eol;
		}
		else if (ab1.windowsize > ab2.windowsize){
			info += PCloudAttributeAndType.WINDOW_ERROR + Global.eol;
		}
		else if (ab1.windowadvancestep > ab2.windowadvancestep) {
			info += PCloudAttributeAndType.WINDOW_ERROR + Global.eol;
		}
		else {
			
			for (int i=0; i<ab2.aggregateattribute.size(); i++) {
				String aname = ab2.aggregateattribute.elementAt(i);
				String afunction = ab2.aggregatefunction.elementAt(i);
				int index;
				if((index = ab1.aggregateattribute.indexOf(aname))!=-1){
					if ((ab1.aggregatefunction.elementAt(index)).equals(afunction)){
						reportattribute.add(aname);
						reportfunction.add(afunction);						
					}
					else{
						unreportattribute.add(aname);
						unreportfunction.add(afunction);
					}
				}
				else {
					unreportattribute.add(aname);
					unreportfunction.add(afunction);
				}
			}
			
		}
		
		if (reportattribute.isEmpty()) {
			
			info += PCloudAttributeAndType.NO_RESULT_WARNING + Global.eol;
		}
		
		else if (!unreportattribute.isEmpty()){
			
			info += PCloudAttributeAndType.PARTIAL_RESULT_WARNING + Global.eol;
			info += "Following attributes with requested aggregation function will not be reported: " + Global.eol;
			for (int i=0; i<unreportattribute.size(); i++){
				info += unreportattribute.elementAt(i) + "(" + unreportfunction.elementAt(i)+ ")" + Global.eol;
			}
		}
		
		else {
			
		}
		
		result.mergeMsg = info;
		
		result.windowtype = ab2.windowtype;
		result.windowsize = ab2.windowsize;
		result.windowadvancestep = ab2.windowadvancestep;
		
		result.aggregateattribute=reportattribute;
		result.aggregatefunction=reportfunction;
		
		return result;
		
	}
	
	
	public static QueryGraph mergeQueryGraph(QueryGraph qg1, QueryGraph qg2){
		
		QueryGraph result = new QueryGraph();
		
		return result;
	}
	
	
	/*
	
	public static void main(String[] args){
				
		String[] s1 = {"a1","a2","a3","a4","a5","a6","a7","a8","a9"};
		String[] s2 = {"a12","a30","a4","a43","a1","a44"};
		
		Vector<String> v1 = new Vector<String>();
		Vector<String> v2 = new Vector<String>();

		for(int i=0; i<s1.length;i++){
			v1.add(s1[i]);
		}
		
		for(int i=0; i<s2.length;i++){
			v2.add(s2[i]);
		}
		
		MapBox mb1 = new MapBox();
		mb1.attributes=v1;
		
		MapBox mb2 = new MapBox();
		mb2.attributes=v2;
				
		MapBox mb3 = Auxiliary.mergeMapBox(mb1, mb2);
		System.out.println(mb3.mergeMsg);
		mb3.print();
		
	
				
	}
	*/
}
